package ddz.logic.login;

import ch.qos.logback.classic.Logger;
import com.kaka.notice.Message;
import com.kaka.notice.annotation.Handler;
import com.kaka.util.Charsets;
import com.kaka.util.MathUtils;
import com.kaka.util.StringUtils;
import ddz.constants.Crypto;
import ddz.constants.ErrCode;
import ddz.constants.ErrLevel;
import ddz.constants.OpCode;
import ddz.core.Table;
import ddz.core.TableState;
import ddz.db.dao.TableDao;
import ddz.db.dao.UserDao;
import ddz.db.entity.UserInfo;
import ddz.net.CtxManager;
import ddz.net.LogicDataHandler;
import ddz.net.ProtocolMessage;
import ddz.protos.Login;
import ddz.utils.DES;
import ddz.utils.DateTimeUtils;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.LoggerFactory;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.Map;

/**
 * 用户登录
 *
 * @author zkpursuit
 */
@Handler(cmd = OpCode.cmd_login)
public class LoginHandler extends LogicDataHandler {

    private static final Logger logger = (Logger) LoggerFactory.getLogger(LoginHandler.class);

    @Override
    public void execute(ProtocolMessage msg) throws Exception {
        byte[] bytes = (byte[]) msg.getBody();
        Login.CsLogin csLogin = Login.CsLogin.parseFrom(bytes);
        String code = csLogin.getCode();

        Map<String, Object> wxUser;
        UserDao userDao = this.retrieveProxy(UserDao.class);
        UserInfo userInfo = null;
        String unionid;
        long uid;

        if (csLogin.getTest() == 1) {
            //用户名密码测试登录
            String[] codeParts = code.split("&");
            String username = codeParts[0];
            String password = codeParts[1];
            uid = StringUtils.toNumber(username);
            userInfo = userDao.getUserInfoByUserId(uid);
            if (userInfo == null) {
                unionid = StringUtils.randomString(16, true);
            } else {
                unionid = userInfo.getUnionid();
            }
            wxUser = new HashMap<>(4);
            wxUser.put("nickname", username);
            wxUser.put("sex", MathUtils.random(1, 2));
            wxUser.put("headimgurl", String.valueOf(MathUtils.random(0, 39)));
            wxUser.put("unionid", unionid);
        } else if (csLogin.getTest() == 2) {
            byte[] tokenBytes;
            try {
                tokenBytes = StringUtils.decodeHexToByte(code);
                tokenBytes = DES.decrypt(tokenBytes, Crypto.LOGIN_TOKEN_DES_KEY_BYTES);
            } catch (Exception ex) {
                logger.error("错误的登录令牌 [{}]", code);
                sendError(msg.ctx(), opcode(), ErrLevel.ERROR, ErrCode.login_token_invalid);
                return;
            }
            String token = new String(tokenBytes, Charsets.utf8);
            String[] parts = token.split(":");
            uid = Long.parseLong(parts[0]);
            long validity = Long.parseLong(parts[1]);
            if (validity < System.currentTimeMillis()) {
                logger.error("用户 [{}] 使用等登录令牌 [{}] 已失效", uid, token);
                sendError(msg.ctx(), opcode(), ErrLevel.ERROR, ErrCode.login_token_invalid);
                return;
            }
            userInfo = userDao.getUserInfo(uid);
            if (userInfo == null) {
                logger.error("用户 [{}] 使用等登录令牌 [{}] 已失效", uid, token);
                sendError(msg.ctx(), opcode(), ErrLevel.ERROR, ErrCode.login_token_invalid);
                return;
            }
            wxUser = new HashMap<>(4);
            wxUser.put("nickname", StringUtils.randomString(6, true));
            wxUser.put("sex", MathUtils.random(1, 2));
            wxUser.put("headimgurl", String.valueOf(MathUtils.random(1, 40)));
            wxUser.put("unionid", code);
            unionid = (String) wxUser.get("unionid");
        } else {
            //测试登录
            wxUser = new HashMap<>(4);
            wxUser.put("nickname", StringUtils.randomString(6, true));
            wxUser.put("sex", MathUtils.random(1, 2));
            wxUser.put("headimgurl", String.valueOf(MathUtils.random(0, 39)));
            wxUser.put("unionid", code);
            unionid = (String) wxUser.get("unionid");
            uid = StringUtils.toNumber(unionid);
            userInfo = userDao.getUserInfo(uid);
        }

        this.closeBeforeLogin(msg.ctx(), uid); //处理抢线登录，关闭前面的连接
        if (userInfo == null) {
            String nickname = (String) wxUser.get("nickname");
            int sex = (int) wxUser.get("sex");
            String headimgurl = (String) wxUser.get("headimgurl");

            userInfo = new UserInfo();
            userInfo.setMaxSeriaWin(0);
            userInfo.setSerialWin(0);
            userInfo.setWinCount(0);
            userInfo.setTotalCount(0);
            userInfo.setCreateTimestamp(System.currentTimeMillis());
            userInfo.setDeskId(0);
            userInfo.setGold(MathUtils.random(20000, 800000));
            userInfo.setDiam(MathUtils.random(200000, 1000000));
            userInfo.setFuka(MathUtils.random(10000, 90000));
//            userInfo.setDiam(0);
//            userInfo.setFuka(0);
//            userInfo.setGold(0);
            userInfo.setHeadImg(headimgurl);
            userInfo.setNickname(nickname);
            userInfo.setScore(0);
            userInfo.setSex(sex);
            userInfo.setUid(uid);
            userInfo.setUnionid(unionid);
            userInfo.setItems("[]");
            this.sendMessage(new Message(OpCode.init_user_task, userInfo.getUid()), true);
        } else {
            long deskId = userInfo.getDeskId();
            if (deskId > 0) {
                TableDao tableDao = this.retrieveProxy(TableDao.class);
                Table table = tableDao.getDesk(deskId);
                if (table != null) {
                    if (table.getState() == TableState.none
                            || table.getState() == TableState.jiesuan || table.getState() == TableState.scrap) {
                        userInfo.setDeskId(0);
                    } else {
                        long deskStartTime = table.getCreateTimestamp();
                        if (deskStartTime > 0) {
                            LocalDateTime ldt = DateTimeUtils.toLocalDateTime(deskStartTime, DateTimeUtils.CH_ZONE);
                            long offsetMinutes = DateTimeUtils.between(ldt, LocalDateTime.now(), DateTimeUtils.Interval.MINUTE);
                            if (offsetMinutes >= 15) { //牌局超过15分钟后清除玩家绑定的deskId
                                userInfo.setDeskId(0);
                            }
                        }
                    }
                } else {
                    userInfo.setDeskId(0);
                }
            }
            long loginOffsetTime = System.currentTimeMillis() - userInfo.getLastLoginTimestamp();
            if (loginOffsetTime / 1000 >= 30) { //登录间隔超出30秒
                this.sendMessage(new Message(OpCode.init_user_task, userInfo.getUid()), true);
            }
        }
        userInfo.setLastLoginTimestamp(System.currentTimeMillis());

        if (csLogin.getTest() == 2) {
            if (logger.isInfoEnabled()) {
                logger.info("[{}]使用token[{}]登录，牌桌[{}]", uid, code, userInfo.getDeskId());
            }
        }

        //7天内有效的登录令牌
        ZoneId zone = ZoneId.of("Asia/Shanghai");
        LocalDateTime ldt = LocalDateTime.now(zone);
        ldt = ldt.plusDays(7);
        long millsecs = ldt.atZone(zone).toInstant().toEpochMilli();
        String token = userInfo.getUid() + ":" + millsecs;
        byte[] tokenBytes = token.getBytes(Charsets.utf8);
        tokenBytes = DES.encrypt(tokenBytes, Crypto.LOGIN_TOKEN_DES_KEY_BYTES);
        token = StringUtils.encodeByteToHexString(tokenBytes);
        userInfo.setToken(token);
        userDao.insertOrUpdateUser(userInfo);

        CtxManager ctxManager = CtxManager.instance();
        ctxManager.add(userInfo.getUid(), msg.ctx());
        logger.info("WebSocket信道与用户 [{}] 绑定，客户端连接总数：{}", userInfo.getUid(), ctxManager.getClientCount());

        Login.ScLogin.Builder scBuilder = Login.ScLogin.newBuilder();
        scBuilder.setDeskId(userInfo.getDeskId());
        scBuilder.setDiam(userInfo.getDiam());
        scBuilder.setFuka(userInfo.getFuka());
        scBuilder.setGold(userInfo.getGold());
        scBuilder.setHeadImg(userInfo.getHeadImg());
        scBuilder.setMaxSeriaWin(userInfo.getMaxSeriaWin());
        scBuilder.setNickname(userInfo.getNickname());
        scBuilder.setSerialWin(userInfo.getSerialWin());
        scBuilder.setSex(userInfo.getSex());
        scBuilder.setTotalCount(userInfo.getTotalCount());
        scBuilder.setUid(userInfo.getUid());
        scBuilder.setWinCount(userInfo.getWinCount());
        if (userInfo.getFirstRechDate() != null && userInfo.getFirstRechDate() > 0) {
            scBuilder.setFirstRech(1);
        }
        //重连票据
        //String rebindTicket = userInfo.getUid() + "&" + userInfo.getLastLoginTimestamp();
        //byte[] ticketBytes = rebindTicket.getBytes(StandardCharsets.UTF_8);
        //ticketBytes = DES.encrypt(ticketBytes, Crypto.TICKET_DES_KEY);
        //rebindTicket = StringUtils.encodeByteToHexString(ticketBytes);
        scBuilder.setTicket(userInfo.getToken());
        //登录令牌
        scBuilder.setToken(userInfo.getToken());

        byte[] respBytes = scBuilder.build().toByteArray();
        this.sendData(userInfo.getUid(), Crypto.isCrypto, opcode(), respBytes);
    }

    private boolean closeBeforeLogin(ChannelHandlerContext currCtx, long uid) {
        CtxManager ctxManager = CtxManager.instance();
        ChannelHandlerContext oldCtx = ctxManager.remove(uid);
        if (oldCtx != null) {
            sendError(oldCtx, opcode(), ErrLevel.ERROR, ErrCode.login_invalid);
            long lastTime = ctxManager.getLastTime(oldCtx);
            if (lastTime <= 0) {
                ctxManager.setLastTime(currCtx, System.currentTimeMillis());
                ctxManager.setOnlineTime(currCtx, 0);
            } else {
                ctxManager.setLastTime(currCtx, lastTime);
                long onlineTime = ctxManager.getOnlineTime(oldCtx);
                if (onlineTime <= 0) {
                    onlineTime = 0;
                } else {
                    long offset = System.currentTimeMillis() - lastTime;
                    onlineTime += offset;
                }
                ctxManager.setOnlineTime(currCtx, onlineTime);
            }
            ctxManager.remove(oldCtx);
            oldCtx.close();
            return true;
        }
        ctxManager.setLastTime(currCtx, System.currentTimeMillis());
        ctxManager.setOnlineTime(currCtx, 0);
        return false;
    }
}
